﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Algoritmia.Problems.Puzzles.NQueens;
using Demos.Problems.Nqueens;

namespace Demos.Puzzles.Nqueens
{
    public class Casilla : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public Brush Fondo { get; set; }
        private Visibility _reina;
        public Visibility Reina
        {
            get { return _reina; }
            set { _reina = value; NotifyPropertyChanged("Reina"); }
        }

        public Casilla(Brush fondo)
        {
            Fondo = fondo;
            Reina = Visibility.Hidden;
        }
    }
    
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private int _reinasColocadas;

        private int _numeroReinas;
        public int NumeroReinas
        {
            get { return _numeroReinas; }
            set
            {
                _numeroReinas = value;
                NotifyPropertyChanged("NumeroReinas");
                Redimensionar();
            }
        }

        private int _algoritmoSeleccionado;
        public int AlgoritmoSeleccionado
        {
            get { return _algoritmoSeleccionado; }
            set
            {
                _algoritmoSeleccionado = value;
                NotifyPropertyChanged("AlgoritmoSeleccionado");
            }
        }

        public string[] Tiempos { get; private set; }

        private ObservableCollection<Casilla> _tablero;
        public ObservableCollection<Casilla> Tablero
        {
            get { return _tablero; }
            set
            {
                _tablero = value;
                NotifyPropertyChanged("Tablero");
            }
        }

        private double _duracionAnimacion;
        public double DuracionAnimacion
        {
            get { return _duracionAnimacion; }
            set
            {
                _duracionAnimacion = value;
                NotifyPropertyChanged("DuracionAnimacion");
            }
        }

        public delegate void ReinaHandler(int posicion);

        public event ReinaHandler SinSolucion;

        public ICommand PosicionarCommand { get; private set; }
        public ICommand MedirCommand { get; private set; }

        private bool _puedoResolver;
        private List<int> _movimientosSolucion;
        private List<int> _colocaciones;
        private InteractivePause _pausa;


        public ViewModel()
        {
            Tablero = new ObservableCollection<Casilla>();
            NumeroReinas = 5;
            DuracionAnimacion = 500;
            _reinasColocadas = 0;
            Tiempos = new string[6];
            SinSolucion += ErrorSolucion;
            _puedoResolver = true;
            PosicionarCommand = new RelayCommand(Resolver, () => _puedoResolver);
            MedirCommand = new RelayCommand(Medir, () => true);
            _colocaciones = new List<int>();
            _pausa = new InteractivePause();
        }


        private void Redimensionar()
        {
            _reinasColocadas = 0;
            _puedoResolver = true;
            Tablero = new ObservableCollection<Casilla>();
            var color = Brushes.Black;

            for (var i = 0; i < NumeroReinas; i++)
            {
                for (var j = 0; j < NumeroReinas; j++)
                {
                    color = (color == Brushes.Black) ? Brushes.White : Brushes.Black;
                    Tablero.Add(new Casilla(color));
                }

                if (NumeroReinas % 2 == 0)
                {
                    color = (color == Brushes.Black) ? Brushes.White : Brushes.Black;
                }
            }
        }

        private void Resolver()
        {
            _movimientosSolucion = new List<int>();
            _puedoResolver = false;
            switch (AlgoritmoSeleccionado)
            {
                case 0:
                    var enumerador1 = new ObservableNQueensEnumerator1();
                    var espacio1 = new NQueensStateSpace1(NumeroReinas);
                    enumerador1.ColocarReina += PosicionarReina;
                    enumerador1.RetirarReina += BorrarReina;
                    var solucion1 = enumerador1.Enumerate(espacio1);
                    if (solucion1.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    break;
                case 1:
                    var enumerador2 = new ObservableNQueensEnumerator2();
                    var espacio2 = new NQueensStateSpace2(NumeroReinas);
                    enumerador2.ColocarReina += PosicionarReina;
                    enumerador2.RetirarReina += BorrarReina;
                    var solucion2 = enumerador2.Enumerate(espacio2);
                    if (solucion2.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    break;
                case 2:
                    var enumerador3 = new ObservableNQueensEnumerator3();
                    var espacio3 = new NQueensStateSpace3(NumeroReinas);
                    enumerador3.ColocarReina += PosicionarReina;
                    enumerador3.RetirarReina += BorrarReina;
                    var solucion3 = enumerador3.Enumerate(espacio3);
                    if (solucion3.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    break;
                case 3:
                    var enumerador4 = new ObservableNQueensEnumerator4();
                    var espacio4 = new NQueensStateSpace4(NumeroReinas);
                    enumerador4.ColocarReina += PosicionarReina;
                    enumerador4.RetirarReina += BorrarReina;
                    var solucion4 = enumerador4.Enumerate(espacio4);
                    if (solucion4.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    break;
                case 4:
                    var enumerador5 = new ObservableNQueensEnumerator5();
                    var espacio5 = new NQueensStateSpace5(NumeroReinas);
                    enumerador5.ColocarReina += PosicionarReina;
                    enumerador5.RetirarReina += BorrarReina;
                    var solucion5 = enumerador5.Enumerate(espacio5);
                    if (solucion5.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    break;
                case 5:
                    var enumerador6 = new NQueensDLX2Solver();
                    var solucion6 = enumerador6.FirstSolution(NumeroReinas);
                    if (solucion6.Count() == 0)
                    {
                        SinSolucion(-1);
                        return;
                    }
                    foreach (var i in solucion6)
                    {
                        _movimientosSolucion.Add(i);
                    }
                    break;
            }
            EjecutarMovimiento(null, null);
        }

        private void PosicionarReina(int fila)
        {
            _movimientosSolucion.Add(fila);
        }

        private void BorrarReina(int fila)
        {
            _movimientosSolucion.Add(-1);
        }

        private void PonerReinaEnTablero(int fila)
        {
            var columna = _reinasColocadas;
            Tablero[fila*NumeroReinas+columna].Reina = Visibility.Visible;
            _colocaciones.Add(fila*NumeroReinas + columna);
            _reinasColocadas++;
        }

        private void QuitarReinaDeTablero(int dato)
        {
            Tablero[_colocaciones.Last()].Reina = Visibility.Hidden;
            _colocaciones.RemoveAt(_colocaciones.Count-1);
            _reinasColocadas--;
        }

        private void EjecutarMovimiento(object o, EventArgs e)
        {
            if (_movimientosSolucion.Count <= 0)
            {
                return;
            }
            var mov = _movimientosSolucion.First();
            _movimientosSolucion.RemoveAt(0);
            if (mov < 0)
            {
                QuitarReinaDeTablero(mov);
            }
            else
            {
                PonerReinaEnTablero(mov);
            }

            _pausa.Pause(DuracionAnimacion, EjecutarMovimiento);
        }

        private static void ErrorSolucion(int codError)
        {
            MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
        }       

        private void Medir()
        {
            MedirB1();
            MedirB2();
            MedirB3();
            MedirB4();
            MedirB5();
            MedirDlx();
        }

        private void MedirB1()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba1 = new Task(() =>
            {
                var enumerador = new NQueensEnumerator1();
                var espacio = new NQueensStateSpace1(NumeroReinas);
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.Enumerate(espacio);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[0] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba1.Start();
        }

        private void MedirB2()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba2 = new Task(() =>
            {
                var enumerador = new NQueensEnumerator2();
                var espacio = new NQueensStateSpace2(NumeroReinas);
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.Enumerate(espacio);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[1] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba2.Start();
        }

        private void MedirB3()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba3 = new Task(() =>
            {
                var enumerador = new NQueensEnumerator3();
                var espacio = new NQueensStateSpace3(NumeroReinas);
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.Enumerate(espacio);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[2] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba3.Start();
        }

        private void MedirB4()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba4 = new Task(() =>
            {
                var enumerador = new NQueensEnumerator4();
                var espacio = new NQueensStateSpace4(NumeroReinas);
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.Enumerate(espacio);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[3] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba4.Start();
        }

        private void MedirB5()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba5 = new Task(() =>
            {
                var enumerador = new SpecialNQueensEnumerator();
                var espacio = new NQueensStateSpace5(NumeroReinas);
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.Enumerate(espacio);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[4] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba5.Start();
        }

        private void MedirDlx()
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba6 = new Task(() =>
            {
                var enumerador = new NQueensDLX2Solver();
                var tiempo1 = DateTime.Now;
                try
                {
                    enumerador.FirstSolution(NumeroReinas);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show("Sin Solución", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => Tiempos[5] = tiempoAcumulado + " ms"));
                NotifyPropertyChanged("Tiempos");
                return;
            });
            prueba6.Start();
        }
    }
}
